﻿/*
 * This file is a part of the Smug# open source project.
 * 
 * Copyright (C) 2010, Troy Simpson 
 * 
 * Project Home: http://www.ebswift.com
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 * 
 */

/*
 * Some info about binding a custom object to a combobox
 * http://phacker.wordpress.com/2007/05/02/binding-custom-objects-to-combobox-in-wpf/
 * 
 * Info on binding images to a listbox
 * http://www.codeproject.com/KB/WPF/CustomListBoxLayoutInWPF.aspx
 * 
 * Info on threading
 * http://www.albahari.com/threading/
 */

using System;
using System.Threading;
using System.Collections.Generic;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Xml;
using FlickrNet;
using System.Windows.Media.Imaging;
using Ebswift;
using System.Data;
using System.Data.SQLite;
using System.IO;

namespace copy2smug
{
    //TODO: logic for hiding and showing the Copy button is incomplete
    //TODO: no progress indicator when copying yet

    /// <summary>
    /// Interaction logic for Window1.xaml
    /// </summary>
    public partial class Window1 : Window
    {
        private string apiKey = File.ReadAllText("smugapikey.txt"); // your smugmug apikey
        private string apiURL;
        private string m_Session = "";
        private bool m_smugmugStatus = false;
        Auth auth; // flickr auth token
        Flickr flickr = new Flickr(File.ReadAllText("flickrapikey.txt"), File.ReadAllText("flickrshared.txt")); // use your flickr api key and flickr shared secred
        private string frob;
        SmugMug m_smugmug;
        private string databaseFile = "copy2smug.db3";
        //static AutoResetEvent cacheSmugMugEvent;
        //public event EventHandler CacheImagesComplete; // used to provide a callback for caching smugmug images

        public Window1()
        {
            InitializeComponent();

            apiURL = "http://api.smugmug.com/services/api/rest/" + SmugMug.apiVersion + "/?APIKey=" + apiKey;

            txtsmuser.Focus();

            // setup smugmug event handlers
            SmugMugImages.Started += new EventHandler(SmugMugImages_Started);
            SmugMugImages.Complete += new EventHandler(SmugMugImages_Complete);

            // setup flickr event handlers
            FlickrPhotos.Started += new EventHandler(FlickrPhotos_Started);
            FlickrPhotos.Complete += new EventHandler(FlickrPhotos_Complete);
        }

        Thread t_cacheSmugMug;

        /// <summary>
        /// Autenticate SmugMug and Flickr.  Flickr can only be authenticated via a web interaction.
        /// </summary>
        private void doLogin()
        {
            /* smugmug auth */
            XmlReader rd = new XmlTextReader(apiURL + "&method=smugmug.login.withPassword&EmailAddress=" + txtsmuser.Text + "&Password=" + txtPassword.Password);

            if (!validateREST(rd))
            {
                MessageBox.Show("SmugMug authentication failed.");

                return;
            }

            rd.ReadToDescendant("Session");
            m_Session = rd["id"];
            m_smugmugStatus = true;

            // do flickr login (2 step process)
            frob = flickr.AuthGetFrob();
            string url = flickr.AuthCalcUrl(frob, AuthLevel.Write);

            flickrAuth flickrauth = new flickrAuth();
            flickrauth.webBrowser1.Url = new Uri(url);
            flickrauth.ShowDialog();

            try
            {
                auth = flickr.AuthGetToken(frob);

                cboSet.ItemsSource = FlickrSets.LoadSets(flickr);
                cboSet.DisplayMemberPath = "Title";
            }
            catch (Exception ex)
            {
                MessageBox.Show("You need to authorise flickr before you can copy images from it.");

                return;
            }

            if (t_cacheSmugMug != null)
                if (t_cacheSmugMug.IsAlive)
                    t_cacheSmugMug.Abort();

            t_cacheSmugMug = new Thread(cacheSmugMug);
            t_cacheSmugMug.Start(this);
        }

        private void cacheSmugMug(object state)
        {
            Window1 w1 = (Window1)state;

            w1.cpSmugTree.Dispatch((cp, v) => cp.Visibility = v, Visibility.Visible);

            m_smugmug = new SmugMug(databaseFile, m_Session, false, apiKey);

            m_smugmug.InitTree();

            populateTree(this);

            w1.cpSmugTree.Dispatch((cp, v) => cp.Visibility = v, Visibility.Hidden);

        }

        private void populateTree(object state)
        {
            Window1 w1 = (Window1)state;
            w1.treeView1.Dispatch(tv => tv.Items.Clear());

            w1.Dispatch(w =>
            {
                SQLiteDataAdapter da = new SQLiteDataAdapter();

                m_smugmug.Command.Connection.Open();

                m_smugmug.DoSmugMug(new RESTType.GetAlbums());

                DataSet dsCategory = new DataSet();
                SQLiteParameter userparam = new SQLiteParameter("@SMUser", m_smugmug.UserID);

                m_smugmug.Command.Parameters.Clear();
                m_smugmug.Command.Parameters.Add(userparam);
                m_smugmug.Command.CommandText = "Select SMUser, id, Name, CategoryID, SubCategories, Albums From TopLevelCategories As Category Where SMUser=@SMUser";
                da.SelectCommand = m_smugmug.Command;
                da.Fill(dsCategory);
                dsCategory.Tables[0].TableName = "Category";

                foreach (DataRow dr in dsCategory.Tables[0].Rows)
                {
                    TreeViewItem node = new TreeViewItem();
                    node.Header = dr["Name"];
                    treeView1.Items.Add(node);

                    // do subcategories
                    if ((long)dr["SubCategories"] > 0)
                    {
                        m_smugmug.Command.Parameters.Clear();
                        DataSet dsSubcat = new DataSet();
                        SQLiteParameter subcatparam = new SQLiteParameter("@CategoryID", (long)dr["id"]);
                        m_smugmug.Command.Parameters.Add(subcatparam);
                        m_smugmug.Command.CommandText = "Select id, Name, CategoryID, AlbumCount From Category Where CategoryID=@CategoryID";
                        da.SelectCommand = m_smugmug.Command;
                        da.Fill(dsSubcat);
                        dsSubcat.Tables[0].TableName = "Category";

                        foreach (DataRow subcatdr in dsSubcat.Tables[0].Rows)
                        {
                            TreeViewItem subnode1 = new TreeViewItem();
                            subnode1.Header = subcatdr["Name"];
                            node.Items.Add(subnode1);

                            // do subcategory albums
                            if ((long)subcatdr["AlbumCount"] > 0)
                            {
                                m_smugmug.Command.Parameters.Clear();
                                DataSet dsSubcatalb = new DataSet();
                                SQLiteParameter subcatalbparam = new SQLiteParameter("@CategoryID", (long)subcatdr["id"]);
                                m_smugmug.Command.Parameters.Add(subcatalbparam);
                                m_smugmug.Command.CommandText = "Select id, Title, Key From Album Where CategoryID=@CategoryID";
                                da.SelectCommand = m_smugmug.Command;
                                da.Fill(dsSubcatalb);
                                dsSubcatalb.Tables[0].TableName = "Album";

                                foreach (DataRow subcatalbdr in dsSubcatalb.Tables[0].Rows)
                                {
                                    TreeViewItem subnode2 = new TreeViewItem();
                                    subnode2.Header = subcatalbdr["Title"];
                                    subnode2.Tag = new Album((long)subcatalbdr["id"], (string)subcatalbdr["Key"]);
                                    subnode1.Items.Add(subnode2);
                                }
                            }
                        }
                    }

                    // do category albums
                    if ((long)dr["Albums"] > 0)
                    {
                        m_smugmug.Command.Parameters.Clear();
                        DataSet dscatalb = new DataSet();
                        SQLiteParameter albparam = new SQLiteParameter("@CategoryID", (long)dr["id"]);
                        m_smugmug.Command.Parameters.Add(albparam);
                        m_smugmug.Command.CommandText = "Select id, Title, Key From Album Where CategoryID=@CategoryID";
                        da.SelectCommand = m_smugmug.Command;
                        da.Fill(dscatalb);
                        dscatalb.Tables[0].TableName = "Album";

                        foreach (DataRow albdr in dscatalb.Tables[0].Rows)
                        {
                            TreeViewItem subnode3 = new TreeViewItem();
                            subnode3.Header = albdr["Title"];
                            subnode3.Tag = new Album((long)albdr["id"], (string)albdr["Key"]);
                            node.Items.Add(subnode3);
                        }
                    }
                }

                m_smugmug.Command.Connection.Close();
            }
            );
        }

        /// <summary>
        /// Validates a REST call.
        /// </summary>
        /// <param name="rd"></param>
        /// <returns>true for an "ok" status, false for any other status.</returns>
        private bool validateREST(XmlReader rd)
        {
            rd.ReadToDescendant("rsp");
            if (rd["stat"] == "ok")
            {
                m_smugmugStatus = true;

                return true;
            }

            m_smugmugStatus = false;

            return false;
        }

        private void btnGo_Click(object sender, RoutedEventArgs e)
        {
            doLogin();
        }

        /// <summary>
        /// Enter key capture to fire off authentication.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void txtPassword_KeyUp(object sender, KeyEventArgs e)
        {
            if (e.Key == Key.Enter)
                btnGo_Click(null, null);
        }

        /// <summary>
        /// Starts the copy process.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btnCopy_Click(object sender, RoutedEventArgs e)
        {
            CopytoSmugMug();
        }

        /// <summary>
        /// Copy selected images to the selected SmugMug gallery.
        /// </summary>
        private void CopytoSmugMug()
        {
            // instructions:
            // 1. enter smugmug credentials and click the Go button - authorise flickr
            // 2. enter the SmugMug album id - only use the first part of the id, leave off everything after and including the underscore
            // 3. enter the full address to the flickr image - first view the original size image in flickr, then view source to get its address
            // 4. click copy

            TreeViewItem tvi = (TreeViewItem)treeView1.SelectedItem;
            Album alb;

            // first, some sanity checks
            if (tvi == null)
            {
                MessageBox.Show("You need to select a valid target album first.");
                return;
            }

            if (tvi.Tag == null)
            {
                MessageBox.Show("You need to select a valid target album first.");
                return;
            }

            if (listFlickr.SelectedItems.Count == 0)
            {
                MessageBox.Show("You need to select images to copy first.");
                return;
            }

            alb = (Album)tvi.Tag;

            foreach (FlickrPhoto fp in listFlickr.SelectedItems)
            {
                XmlReader rd = new XmlTextReader(apiURL + "&method=smugmug.images.uploadFromURL&SessionID=" + m_Session + "&AlbumID=" + alb.Id + "&URL=" + fp.PhotoInfo.OriginalUrl);

                if (!validateREST(rd))
                {
                    MessageBox.Show("Copy from URL failed.");

                    return;
                }
            }

            treeView1_SelectedItemChanged(null, null);
            //listSmug.Items.Refresh();
        }

        /// <summary>
        /// Display the images for the selected Flickr set.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void cboSet_SelectionChanged(object sender, SelectionChangedEventArgs e)
        {
            cboSet.DisplayMemberPath = "Title";
            ComboBox cbo = cboSet;

            Photoset ps = (Photoset)cbo.SelectedItem;

            FlickrPhotos.LoadPhotos(flickr, (Photoset)ps, this);
        }

        private void FlickrPhotos_Started(object sender, EventArgs e)
        {
            btnCopy.Visibility = Visibility.Hidden;
            cpFlickr.Visibility = Visibility.Visible;
        }

        private void FlickrPhotos_Complete(object sender, EventArgs e)
        {
            btnCopy.Visibility = Visibility.Visible;

            listFlickr.ItemsSource = FlickrPhotos.PhotoList;
            cpFlickr.Visibility = Visibility.Hidden;
        }

        /// <summary>
        /// Update the display of SmugMug gallery thumbnails from the selected Album.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void treeView1_SelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
        {
            cpSmugPics.Visibility = Visibility.Visible;

            Thread t_showSmugMugAlbum = new Thread(showSmugAlbum);

            t_showSmugMugAlbum.Start(this);
        }

        private static readonly object lock_SmugMugAlbum = new object();

        private void showSmugAlbum(object state)
        {
            lock (lock_SmugMugAlbum)
            {
                Window1 w1 = (Window1)state;

                w1.Dispatch(w => listSmug.ItemsSource = null);

                long albid = -1;
                Album alb = new Album(-1, "");

                var action = new Action<Control>(c =>
                {
                    TreeViewItem tvi = (TreeViewItem)treeView1.SelectedItem;

                    if (tvi.Tag != null)
                    {
                        alb = (Album)tvi.Tag;

                        albid = alb.Id;
                    }
                });
                treeView1.Dispatcher.Invoke(action, treeView1);

                if (alb != null)
                {
                    while (m_smugmug.Command.Connection.State == ConnectionState.Open)
                    {
                        //TODO: whilst this reduces errors, it does not eliminate them - there doesn't seem to be a way to check if sqlite is busy shutting down from a previous thread

                        // the connection can still be closing from a previous thread
                        Thread.Sleep(200);
                    }

                    // we want to clear the image cache for a reload now that we have added a new image
                    m_smugmug.ClearImageCache(alb.Id);
                    m_smugmug.Command.Connection.Open();
                    m_smugmug.DoSmugMug(new RESTType.GetImages(), alb.Id.ToString(), alb.Key);

                    SmugMugImages.LoadPhotos(m_smugmug, alb.Id, this);

                    m_smugmug.Command.Connection.Close();
                }
            }
        }

        private void SmugMugImages_Started(object sender, EventArgs e)
        {
            btnCopy.Visibility = Visibility.Hidden;

            //cpSmugPics.Visibility = Visibility.Visible;
        }

        private void SmugMugImages_Complete(object sender, EventArgs e)
        {
            btnCopy.Visibility = Visibility.Visible;
            cpSmugPics.Visibility = Visibility.Hidden;
            listSmug.ItemsSource = SmugMugImages.PhotoList;
        }
    }
}